export class Vec2 {
  x: number;
  y: number;

  get length() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }

  get normal() {
    return this.clone().normalize();
  }

  static lerp(t: number, v1: Vec2, v2: Vec2) {
    return new Vec2(v1.x * (1 - t) + v2.x * t, v1.y * (1 - t) + v2.y * t);
  }

  static turnRight(v1: Vec2, v2: Vec2) {
    return v1.cross(v2) < 0;
  }

  static turnLeft(v1: Vec2, v2: Vec2) {
    return v1.cross(v2) > 0;
  }

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  clone() {
    return new Vec2(this.x, this.y);
  }

  add(other: Vec2) {
    return new Vec2(this.x + other.x, this.y + other.y);
  }

  sub(other: Vec2) {
    return new Vec2(this.x - other.x, this.y - other.y);
  }

  dot(other: Vec2) {
    return this.x * other.x + this.y * other.y;
  }

  cross(other: Vec2) {
    return this.x * other.y - this.y * other.x;
  }

  mul(t: number) {
    return new Vec2(this.x * t, this.y * t);
  }

  div(t: number) {
    return new Vec2(this.x / t, this.y / t);
  }

  normalize() {
    return this.div(this.length);
  }

  prep() {
    return new Vec2(-this.y, this.x);
  }

  equals(other: Vec2) {
    return this === other || (this.x === other.x && this.y === other.y);
  }

  toString() {
    return `{${this.x},${this.y}}`;
  }
}

export class Segment {
  p1: Vec2;
  p2: Vec2;

  constructor(p1: Vec2, p2: Vec2) {
    this.p1 = p1;
    this.p2 = p2;
  }

  get vec() {
    return this.p2.sub(this.p1);
  }
}

export class Line2 {
  p: Vec2;
  vec: Vec2;

  constructor(p: Vec2, vec: Vec2) {
    this.p = p;
    this.vec = vec.normal;
  }

  static from2Points(p1: Vec2, p2: Vec2) {
    return new Line2(p1, p2.sub(p1));
  }

  /**
   * 计算两直线交点
   * @date 2023/12/4 - 18:13:52
   *
   * @static
   * @param {Line2} l1
   * @param {Line2} l2
   * @returns {Vec2}
   */
  static intersect(l1: Line2, l2: Line2) {
    let cross = l1.vec.cross(l2.vec);
    if (cross === 0) {
      throw "line parallel!!!";
    }

    let d = l2.p.sub(l1.p).cross(l2.vec);
    return l1.p.add(l1.vec.mul(d / cross));
  }
}

export class Path {
  points: Vec2[] = [];

  static from(coordinates: number[]) {
    let path = new Path();
    let size = Math.floor(coordinates.length / 2);
    for (let i = 0; i < size; i++) {
      path.points.push(new Vec2(coordinates[2 * i], coordinates[2 * i + 1]));
    }
    return path;
  }

  floor() {
    for (const v of this.points) {
      v.x = Math.floor(v.x);
      v.y = Math.floor(v.y);
    }
    return this;
  }

  round() {
    for (const v of this.points) {
      v.x = Math.round(v.x);
      v.y = Math.round(v.y);
    }
    return this;
  }

  reverse() {
    this.points.reverse();
    return this;
  }

  clone() {
    let newPath = new Path();
    for (const v of this.points) {
      newPath.points.push(v.clone());
    }
    return newPath;
  }

  toString() {
    return this.points.join(",");
  }
}

/**
 * 矩形
 * @date 2023/12/4 - 15:31:31
 *
 * @export
 * @class Rect
 * @typedef {Rect}
 */
export class Rect {
  xmin: number = Infinity;
  ymin: number = Infinity;
  xmax: number = -Infinity;
  ymax: number = -Infinity;

  get valid() {
    return this.xmax >= this.xmin && this.ymax >= this.ymin;
  }

  get area() {
    return this.valid ? (this.xmax - this.xmin) * (this.ymax - this.ymin) : 0;
  }

  get bl() {
    return new Vec2(this.xmin, this.ymin);
  }

  get tl() {
    return new Vec2(this.xmin, this.ymax);
  }

  get br() {
    return new Vec2(this.xmax, this.ymin);
  }

  get tr() {
    return new Vec2(this.xmax, this.ymax);
  }

  expandPoint(p: Vec2) {
    if (p.x < this.xmin) {
      this.xmin = p.x;
    }

    if (p.x > this.xmax) {
      this.xmax = p.x;
    }

    if (p.y < this.ymin) {
      this.ymin = p.y;
    }

    if (p.y > this.ymax) {
      this.ymax = p.y;
    }
  }
}

/**
 * 平行四边形
 * @date 2023/12/4 - 15:31:16
 *
 * @export
 * @class Parallelogram
 * @typedef {Parallelogram}
 */
export class Parallelogram {
  a: Vec2;
  b: Vec2;
  c: Vec2;

  constructor(a: Vec2, b: Vec2, c: Vec2) {
    this.a = a;
    this.b = b;
    this.c = c;
  }

  get d() {
    return this.a.add(this.l2);
  }

  get l1() {
    return this.b.sub(this.a);
  }

  get l2() {
    return this.c.sub(this.b);
  }

  get area() {
    return Math.abs(this.l1.cross(this.l2));
  }
}
